package de.fhtrier.mp.wollmaus.entity;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Vector2f;

import de.fhtrier.mp.wollmaus.constants.Constants;
import de.fhtrier.mp.wollmaus.constants.PlayerConstants;
import de.fhtrier.mp.wollmaus.entity.player.PlayerAnimations;
import de.fhtrier.mp.wollmaus.entity.player.PlayerDirection;
import de.fhtrier.mp.wollmaus.entity.player.PlayerState;
import de.fhtrier.mp.wollmaus.gamestate.GameState;
import de.fhtrier.mp.wollmaus.map.Hindernis;
import de.fhtrier.mp.wollmaus.util.AssetManager;

public class Player {

	private Vector2f position;

	private PlayerState state;
	private PlayerDirection direction;

	private Animation standRightAni;
	private Animation standLeftAni;
	private Animation runRightAni;
	private Animation runLeftAni;
	private Animation jumpRightAni;
	private Animation jumpLeftAni;
	private Animation fallRightAni;
	private Animation fallLeftAni;

	private final float JUMPSTATE_PERIOD = 850; // Dauer eines Sprungs in Millisekunden

	private int jumpTime;
	private float jumpHeight;

	public boolean animationFertig;

	private GameState main;

	private int collectedNotes;

	// Dummy-Kolisions-Anzeige
	private float dx;
	private float dy;
	private int dwidth;
	private int dheight;

	/**
	 * Init
	 */
	public Player(GameState main) {
		this.main = main;

		position = new Vector2f();
		collectedNotes = 0;
		setState(PlayerState.STAND);
		direction = PlayerDirection.RIGHT;

		setupAnimations();
	}

	public void setupAnimations() {
		PlayerAnimations animationsKlasse = new PlayerAnimations();

		standRightAni = new Animation();
		standLeftAni = new Animation();
		runRightAni = new Animation();
		runLeftAni = new Animation();
		jumpRightAni = new Animation();
		jumpLeftAni = new Animation();
		fallRightAni = new Animation();
		fallLeftAni = new Animation();

		standLeftAni = animationsKlasse.createStandLeftAnimation();
		standRightAni = animationsKlasse.createStandRightAnimation();

		runRightAni = animationsKlasse.createRunRightAnimation();
		runLeftAni = animationsKlasse.createRunLeftAnimation();

		jumpRightAni = animationsKlasse.createJumpRightAnimation();
		jumpLeftAni = animationsKlasse.createJumpLeftAnimation();

		fallRightAni = animationsKlasse.createFallRightAnimation();
		fallLeftAni = animationsKlasse.createFallLeftAnimation();
	}

	public void respawn() {
		collectedNotes = 0;
		setState(PlayerState.STAND);
		direction = PlayerDirection.RIGHT;

		position = new Vector2f(main.getStage().getStartPos().x, main.getStage().getStartPos().y - 85); // Startposition der Spielfigur

		float fallingDist = (PlayerConstants.GRAVITATION_SPEED) / 1000f;
		while (canPass(getX(), getY() + fallingDist)) {
			setPosition(getX(), getY() + fallingDist);
		}
	}

	/**
	 * Wenn Spieler gefailt hat (Klippe runter, falscher Ton), alles resetten mit schwarzem Bildschirm
	 */
	public void reset(String mes1, String mes2) {
		resetJump();
		setState(PlayerState.STAND);
		main.getResetGui().playerFailed(mes1, mes2);
	}

	/**
	 * Rendering
	 */
	public void render(GameContainer gc, Graphics g) throws SlickException {
		// Debug: zeichne PlayerRect
		if (Constants.debug == true) {
			g.setColor(Color.red);
			g.fillRect(getX(), getY(), getWidth(), getHeight());
		}

		switch (getState()) {
		case STAND:
			getStandingAni(getDirection()).draw(getX(), getY());
			break;

		case WALK:
			getRunAni(getDirection()).draw(getX(), getY());
			break;

		case FALL:
			getFallAni(getDirection()).draw(getX(), getY());
			getFallAni(getDirection()).stopAt(getFallAni(getDirection()).getFrameCount() - 1);
			break;

		case JUMP:
		case STRAIGHT_JUMP:
			// damit animation nach dem ersten stoppen erneut abgespielt wird
			if (getJumpAni(getDirection()).isStopped()) {
				getJumpAni(getDirection()).restart();
			}
			getJumpAni(getDirection()).draw(getX(), getY());
			getJumpAni(getDirection()).stopAt(PlayerAnimations.JUMP_PICS - 1);
			break;
		}

		// Debug: zeichnet kollidierendes Objekt
		if (dx != 0 && Constants.debug == true) {
			g.setColor(Color.blue);
			g.fillRect(dx, dy, dwidth, dheight);
		}
	}

	/**
	 * Update
	 */
	public void update(GameContainer gc, int delta) {
		Input input = gc.getInput();

		// nötig, damit die methode setzeSpringZaehlerZurueck() nur aufgerufen wird, wenn
		// die animation fertig abgespielt ist
		boolean zustandwechsel = false;

		// Steuerung wird bei straight jump ausgesetzt
		if (getState() != PlayerState.STRAIGHT_JUMP) {
			if (input.isKeyDown(Input.KEY_RIGHT)) {
				walk(delta, PlayerDirection.RIGHT);
			}

			if (input.isKeyDown(Input.KEY_LEFT)) {
				walk(delta, PlayerDirection.LEFT);
			}

			if (state != PlayerState.JUMP && state != PlayerState.FALL && getState() != PlayerState.STRAIGHT_JUMP && isOnGround(delta)) {
				if (input.isKeyDown(Input.KEY_UP)) {
					zustandwechsel = true;
					setState(PlayerState.STRAIGHT_JUMP);
					AssetManager.getInstance().getSound(AssetManager.SOUND_PLAYER_JUMP).play(1f, Constants.SOUND_VOLUME);
				}
				else if (input.isKeyDown(Input.KEY_SPACE)) {
					zustandwechsel = true;
					setState(PlayerState.JUMP);
					AssetManager.getInstance().getSound(AssetManager.SOUND_PLAYER_JUMP).play(1f, Constants.SOUND_VOLUME);
				}
			}
		}

		if (getState() == PlayerState.JUMP || getState() == PlayerState.STRAIGHT_JUMP) {
			doJump(delta);

			getJumpAni(getDirection()).stopAt(getJumpAni(getDirection()).getFrameCount() - 1);

			if (jumpTime >= JUMPSTATE_PERIOD && zustandwechsel == false && isOnGround(delta) && canPass(getX(), getY())) {
				resetJump();
			}
		}
		// Gravitation
		else {
			float fallingDist = (PlayerConstants.GRAVITATION_SPEED * delta) / 1000f;
			if (canPass(getX(), getY() + fallingDist)) {
				if (getState() != PlayerState.FALL) {
					getFallAni(getDirection()).restart();
					setState(PlayerState.FALL);
					if (canFall(getX(), getY())) {
						AssetManager.getInstance().getSound(AssetManager.SOUND_PLAYER_FALL).play(1f, Constants.SOUND_VOLUME);
					}
				}
				setPosition(getX(), getY() + fallingDist);
			}
			else {
				if (getState() == PlayerState.FALL) {
					setState(PlayerState.STAND);
	
					// Korrektur, wenn nicht direkt aufm Boden
					if (canPass(getX(), getY() + fallingDist / 2f)) {
						setPosition(getX(), getY() + fallingDist / 2f);
					}
				}
				setPosition(Math.abs(getX()), Math.abs(getY())); 
			}
		}

		// wenn keine taste gedrückt ist, zustand wieder auf "stehen" setzen
		// abfrage ob ani gestoppt ist nötig, damit sie bis zum ende abgespielt wird und
		// erst danach der zustand gewechselt wird

		if (!(getState() == PlayerState.FALL || input.isKeyDown(Input.KEY_SPACE) || input.isKeyDown(Input.KEY_UP) || input.isKeyDown(Input.KEY_RIGHT) || input
				.isKeyDown(Input.KEY_LEFT))) {

			// Reset state
			if (getState() != PlayerState.STAND && getState() != PlayerState.JUMP && getState() != PlayerState.STRAIGHT_JUMP)
				setState(PlayerState.STAND);
		}

		// Aus dem Level rausgefallen
		if (getY() > main.getStage().getMap().getHeight() * Constants.TILESIZE) {
			reset(Constants.PLAYER_DIED_MES, Constants.PLAYER_DIED_MES_2);
		}

		handleTeleport();
		handleEntityCollision();
	}

	/**
	 * Walking
	 */

	private void walk(int delta, PlayerDirection dir) {
		if (state == PlayerState.STAND) {
			setState(PlayerState.WALK);
		}

		direction = dir;

		float newX;
		float deltaMovement = (PlayerConstants.MOVEMENT_SPEED * delta) / 1000f;
		if (dir == PlayerDirection.LEFT) {
			newX = getX() - deltaMovement;
		}
		else {
			newX = getX() + deltaMovement;
		}

		if (canPass(newX, getY())) {
			setPosition(newX, getY());
		}

	}

	/**
	 * Jumping
	 */
	boolean fallingAgain;

	public void doJump(int delta) {
		jumpTime += delta;
		float x = jumpTime / JUMPSTATE_PERIOD; // x muss für die Formel zwischen 0 und 1 sein
		if (x > 0.45f && x < 0.55f) {
			x = 0.5f;
		}
		if (x > 1) {
			x = 1f;
		}

		float tmpHeight = jumpHeight;
		jumpHeight = calcJumpHeight(x); // Formel sorgt für einen periodischen Sprung

		float move = jumpHeight - tmpHeight;

		if (move != 0.0f) {
			// durch Kollision fallen, wenn ich noch in einer stecke!
			boolean ignoreColl = false;
			
			// wenn ich mich hochbewege, falle ich nicht
			if (move > 0) {
				fallingAgain = false;
			}
			else if (fallingAgain == false) {
				if (canPass(getX(), getY()) && canPass(getX(), getY() - move)) {
					fallingAgain = true;
				}
				else {
					ignoreColl = true;
				}
			}

			if (canPass(getX(), getY() - move) || ignoreColl) {
				this.setPosition(getX(), getY() - move);
			}
		}
	}

	public void resetJump() {
		jumpTime = 0;
		jumpHeight = 0;
		getJumpAni(getDirection()).restart();
		setState(PlayerState.STAND);
	}

	// Funktion sorgt für einen periodischen Sprung
	// x muss optimalerweise zwischen 0 und 1 liegen
	private float calcJumpHeight(float x) {
		float h = (-((x - 0.5f) * (x - 0.5f)) * 4f + 1f) * PlayerConstants.JUMP_UPSPEED;
//		float max = (-((0.5f - 0.5f) * (0.5f - 0.5f)) * 4f + 1f) * PlayerConstants.JUMP_UPSPEED;
		// Dirty hack, damit die Maxhöhe immer erreicht wird
//		if (h < max && h+1f >= max) {
//			h = max;
//		}
		return h;
	}

	/**
	 * Collision Detection
	 */
	public boolean canPass(float newX, float newY) {
		if (newX < 0 || newY < 0 || newX > main.getStage().getMap().getWidth() * Constants.TILESIZE - getWidth()) {
			return false;
		}
		Rectangle2D.Float playerRect = new Rectangle2D.Float(newX + 10, newY + 10, this.getWidth() - 20, this.getHeight() - 20);
		for (Hindernis h : main.getStage().getHindernisse()) {
			Rectangle hindernisRect = new Rectangle(h.getPunkt().x, h.getPunkt().y, h.getBreite(), h.getHoehe());
			if (hindernisRect.intersects(playerRect) && (getState() != PlayerState.STRAIGHT_JUMP || newY > getY())) {
				dx = hindernisRect.x;
				dy = hindernisRect.y;
				dwidth = hindernisRect.width;
				dheight = hindernisRect.height;
				return false;
			}
		}
		dx = 0;
		return true;
	}

	/**
	 * Berechnet, ob der Player auf sicherem Platteau ist
	 */
	public boolean isOnGround(int delta) {
		float fallingDist = (PlayerConstants.GRAVITATION_SPEED * delta) / 1000f;
		return !canPass(getX(), getY() + fallingDist);
	}

	public boolean canFall(float newX, float newY) {
		if (newX < 0 || newY < 0 || newX > main.getStage().getMap().getWidth() * Constants.TILESIZE - getWidth()) {
			return false;
		}
		Rectangle2D.Float playerRect = new Rectangle2D.Float(newX + 10, newY + 10, this.getWidth() - 20, (this.getHeight() * 3.5f) - 20);
		for (Hindernis h : main.getStage().getHindernisse()) {
			Rectangle hindernisRect = new Rectangle(h.getPunkt().x, h.getPunkt().y, h.getBreite(), h.getHoehe());
			if (hindernisRect.intersects(playerRect) && (getState() != PlayerState.STRAIGHT_JUMP || newY > getY())) {
				dx = hindernisRect.x;
				dy = hindernisRect.y;
				dwidth = hindernisRect.width;
				dheight = hindernisRect.height;
				return false;
			}
		}
		dx = 0;
		return true;
	}

	public int getHeight() {
		return 85; // all anis got now the same size
	}

	public int getWidth() {
		return 85; // all anis got now the same size
	}

	/**
	 * Teleporting
	 */
	public void handleTeleport() {
		Rectangle2D.Float playerRect = new Rectangle2D.Float(getX(), getY(), this.getWidth(), this.getHeight());
		for (Point p : main.getStage().getTeleports()) {
			Rectangle rect = new Rectangle(p.x, p.y, Constants.TILESIZE, Constants.TILESIZE);
			if (rect.intersects(playerRect) && main.getStage().isLevelSolved()) {
				setState(PlayerState.STAND);
				main.changeLevel();
				return;
			}
		}
	}

	/**
	 * Handle object collision
	 */
	public void handleEntityCollision() {
		Rectangle2D.Float playerRect = new Rectangle2D.Float(getX(), getY(), this.getWidth(), this.getHeight());
		for (Note n : main.getStage().getNotes()) {
			if (!n.isCollected() && n.getCollisionRect().intersects(playerRect)) {
				if (n.isCollectable(this)) {
					n.collectSucces();
					n.setCollected(true);
					this.collectedNotes++;
				}
				else {
					n.collectFail();
					reset(Constants.PLAYER_GOT_WRONG_NOTE, Constants.PLAYER_GOT_WRONG_NOTE_2);
				}
			}
		}
	}

	// ////////////////////////////////////////////////////////

	public Animation getStandingAni(PlayerDirection d) {
		if (d == PlayerDirection.RIGHT) {
			return getStandingRightAni();
		}
		else if (d == PlayerDirection.LEFT) {
			return getStandingLeftAni();
		}
		return null;
	}

	public Animation getStandingRightAni() {
		return standRightAni;
	}

	public Animation getStandingLeftAni() {
		return standLeftAni;
	}

	public Animation getRunAni(PlayerDirection d) {
		if (d == PlayerDirection.RIGHT) {
			return getRunRightAni();
		}
		else if (d == PlayerDirection.LEFT) {
			return getRunLeftAni();
		}
		return null;
	}

	public Animation getRunRightAni() {
		return runRightAni;
	}

	public Animation getRunLeftAni() {
		return runLeftAni;
	}

	public Animation getJumpAni(PlayerDirection d) {
		if (d == PlayerDirection.RIGHT) {
			return getJumpRightAni();
		}
		else if (d == PlayerDirection.LEFT) {
			return getJumpLeftAni();
		}
		return null;
	}

	public Animation getJumpRightAni() {
		return jumpRightAni;
	}

	public Animation getJumpLeftAni() {
		return jumpLeftAni;
	}

	public Animation getFallAni(PlayerDirection d) {
		if (d == PlayerDirection.RIGHT) {
			return getFallRightAni();
		}
		else if (d == PlayerDirection.LEFT) {
			return getFallLeftAni();
		}
		return null;
	}

	public Animation getFallRightAni() {
		return fallRightAni;
	}

	public Animation getFallLeftAni() {
		return fallLeftAni;
	}

	public float getX() {
		return position.getX();
	}

	public float getY() {
		return position.getY();
	}

	public void setPosition(float x, float y) {
		position.set(x, y);
	}

	public PlayerState getState() {
		return state;
	}

	public void setState(PlayerState state) {
		if (getState() != state) {
			this.state = state;
		}
	}

	public PlayerDirection getDirection() {
		return direction;
	}

	public void setCollectedNotes(int collectedNotes) {
		this.collectedNotes = collectedNotes;
	}

	public int getCollectedNotes() {
		return collectedNotes;
	}

}
